/*
 * TODO language 
 * Copyright (c) 2010 TODO Team
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include <iostream>
#include <cstdlib>
#include <string>
#include <cmath>
#include "position.hh"
#include "location.hh"
#include "compiler.h"
#include "opcodes.h"

// TodoOpcodes
todo::TodoOpcodes ops;
// Executor
todo::TodoExecutor executor;

namespace todo
{
/**
 * Display the error message
 * @param message the error message
 */
void TodoCompiler::error(const char* message) const
{
	yy::position last = location->end - 1;

	if (is_file) {
		std::cout << "Error: " << message << " in " << *last.filename << " on line " << last.line << std::endl;
	} else {
		std::cout << "Error: " << message << " on line " << last.line << std::endl;
	}
	
	exit(1);
}

/**
 * Changes the variable value
 * e.g. foo = "bar"
 * @param var the variable name
 * @param value the new value
 */
void TodoCompiler::change_var(const TodoData &var, const TodoData& value) const
{
	todo::todo_opcode *opcode = new todo::todo_opcode(TODO_CHG_VAR, var, value);
	
	opcode->set_handler(&todo::TodoExecutor::change_var);
	ops.add(opcode);	
}

/**
 * Makes the possible conversion between constant values
 * e.g. double -> integer
 */
void TodoCompiler::cast_constant_value(TodoType expected_type, TodoData& value) const
{
	bool allowed = false;
	
	if (value.type == TODO_CONSTANT && value.data.type != expected_type) {
		switch (expected_type) {
			case TODO_DOUBLE:
				if (value.data.type == TODO_INTEGER) {
					value.data.type = expected_type;
					value.data.val.tdouble = (double) value.data.val.tint;
					allowed = true;
				}
				break;
			case TODO_INTEGER:
				if (value.data.type == TODO_DOUBLE) {
					value.data.type = expected_type;
					value.data.val.tint = (int) value.data.val.tdouble;
					allowed = true;
				}
				break;
		}
		if (allowed == false) {
			error("Incompatible data type");
		}
	}
}

/**
 * Register variable non-initialized
 * e.g. type name[, ...]
 * @param type the variable type
 * @param name the variable name
 */
void TodoCompiler::register_var(TodoType& type, TodoData& name)
{
	TodoData val(TODO_CONSTANT);
	
	val.data.type = type;
	
	switch (type) {
		case TODO_INTEGER:
			val.data.val.tint = 0;
			break;
		case TODO_DOUBLE:
			val.data.val.tdouble = 0;
			break;
		case TODO_STRING:
			val.data.val.str = new std::string("");
			break;
	}
	
	register_var(type, name, val);
}

/**
 * Register variable initialized
 * e.g. type name = value[, name = value...]
 * @param type the variable type
 * @param name the variable name
 * @param val  the variable value
 */
void TodoCompiler::register_var(TodoType& type, const TodoData& name, TodoData& val)
{
	todo::todo_opcode *opcode = new todo::todo_opcode(TODO_DECL_VAR);
	
	cast_constant_value(type, val);
	
	opcode->set_op1(name);
	opcode->set_op2(val);
	opcode->set_handler(&todo::TodoExecutor::declare_var);
	
	last_type = type;
	
	ops.add(opcode);
}

/**
 * Multiplicates two values
 * expr * expr
 * @param operan1  the first operand
 * @param operand2 the second operand
 */
void TodoCompiler::multiplication(TodoData& operand1, TodoData& operand2, bool assign) const
{
	TodoData& result = operand1;
	
	try {		
		if (operand1.type == TODO_CONSTANT && operand2.type == TODO_CONSTANT) {
			result.data *= &operand2.data;
		} else {
			todo::todo_opcode* opcode = new todo::todo_opcode(operand1, operand2);
			
			opcode->set_type(assign ? TODO_MULT_ASSIGN : TODO_MULT);
			opcode->set_handler(&todo::TodoExecutor::mult_assign);
			ops.add(opcode);
			
			result.type = TODO_EXPR_VALUE;
		}
	} catch (TodoException e) {
		DEL(operand1);
		
		result.type = TODO_CONSTANT;
		result.data.type = TODO_INTEGER;
		result.data.val.tint = 0;
		
		std::cout << e.what() << std::endl;
	}
}

/**
 * Divides two values
 * expr / expr
 * @param operan1  the first operand
 * @param operand2 the second operand
 */
void TodoCompiler::division(TodoData& operand1, TodoData& operand2, bool assign) const
{
	TodoData& result = operand1;
	
	try {
		if (operand1.type == TODO_CONSTANT && operand2.type == TODO_CONSTANT) {
			result.data /= &operand2.data;
		} else {
			todo::todo_opcode* opcode = new todo::todo_opcode(operand1, operand2);

			opcode->set_type(assign ? TODO_DIV_ASSIGN : TODO_DIV);
			opcode->set_handler(&todo::TodoExecutor::div_assign);
			ops.add(opcode);
			
			result.type = TODO_EXPR_VALUE;
		}
	} catch (TodoException e) {
		DEL(operand1);
		
		result.type = TODO_CONSTANT;
		result.data.type = TODO_INTEGER;
		result.data.val.tint = 0;
		
		std::cout << e.what() << std::endl;
	}
}

/**
 * Additions two values
 * expr + expr
 * @param operan1  the first operand
 * @param operand2 the second operand
 */
void TodoCompiler::addition(TodoData& operand1, TodoData& operand2, bool assign) const
{
	TodoData& result = operand1;
	
	try {
		if (operand1.type == TODO_CONSTANT && operand2.type == TODO_CONSTANT) {
			result.data += &operand2.data;
		} else {
			todo::todo_opcode* opcode = new todo::todo_opcode(operand1, operand2);
			
			opcode->set_type(assign ? TODO_ADD_ASSIGN : TODO_ADD);
			opcode->set_handler(&todo::TodoExecutor::add_assign);
			ops.add(opcode);
			
			result.type = TODO_EXPR_VALUE;
		}
	} catch (TodoException e) {
		DEL(operand1);
		
		result.type = TODO_CONSTANT;
		result.data.type = TODO_INTEGER;
		result.data.val.tint = 0;
		
		std::cout << e.what() << std::endl;
	}
}

/**
 * Subtractes two values
 * expr - expr
 * @param operan1  the first operand
 * @param operand2 the second operand
 */
void TodoCompiler::subtraction(TodoData& operand1, TodoData& operand2, bool assign) const
{
	TodoData& result = operand1;
	
	try {
		if (operand1.type == TODO_CONSTANT && operand2.type == TODO_CONSTANT) {
			result.data -= &operand2.data;
		} else {
			todo::todo_opcode* opcode = new todo::todo_opcode(operand1, operand2);
			
			opcode->set_type(assign ? TODO_SUB_ASSIGN : TODO_SUB);
			opcode->set_handler(&todo::TodoExecutor::sub_assign);
			ops.add(opcode);
			
			result.type = TODO_EXPR_VALUE;
		}
	} catch (TodoException e) {
		DEL(operand1);
		
		result.type = TODO_CONSTANT;
		result.data.type = TODO_INTEGER;
		result.data.val.tint = 0;
		
		std::cout << e.what() << std::endl;
	}
}

/**
 * Modules two values
 * expr % expr
 * @param operan1  the first operand
 * @param operand2 the second operand
 */
void TodoCompiler::modulo(TodoData& operand1, TodoData& operand2, bool assign) const
{
	TodoData& result = operand1;
	
	try {
		if (operand1.type == TODO_CONSTANT && operand2.type == TODO_CONSTANT) {
			result.data %= &operand2.data;
		} else {
			todo::todo_opcode* opcode = new todo::todo_opcode(operand1, operand2);
			
			opcode->set_type(assign ? TODO_MOD_ASSIGN : TODO_MOD);
			opcode->set_handler(&todo::TodoExecutor::mod_assign);
			ops.add(opcode);
			
			result.type = TODO_EXPR_VALUE;
		}
	} catch (TodoException e) {
		DEL(operand1);
		
		result.type = TODO_CONSTANT;
		result.data.type = TODO_INTEGER;
		result.data.val.tint = 0;
		
		std::cout << e.what() << std::endl;
	}
}

/**
 * Prints a value
 * print value[, ...]
 * println value[, ...]
 * @param val the value to be printed
 * @param newline if or not add newline
 */
void TodoCompiler::print(TodoData& val, bool newline) const
{
	todo::todo_opcode *opcode = new todo::todo_opcode;

	opcode->set_type(newline ? todo::TODO_PRINTLN : todo::TODO_PRINT);
	opcode->set_op1(val);
	opcode->set_handler(newline ? &todo::TodoExecutor::println : &todo::TodoExecutor::print);
	
	ops.add(opcode);
}

/**
 * Returns
 * return
 */
void TodoCompiler::ret(void) const
{
	todo::todo_opcode* opcode = new todo::todo_opcode(TODO_RETURN);
	
	opcode->set_handler(&todo::TodoExecutor::ret);
	
	ops.add(opcode);	
}

/**
 * Returns a value
 * return expr
 * @param expr the expression
 */
void TodoCompiler::ret(TodoData& expr) const
{
	todo::todo_opcode* opcode = new todo::todo_opcode(TODO_RETURN);
	
	opcode->set_op1(expr);
	opcode->set_handler(&todo::TodoExecutor::ret);
	
	ops.add(opcode);
}

void TodoCompiler::method_call(TodoData& obj, TodoData& method_name)
{
	todo_opcode* opcode = new todo_opcode(TODO_METHOD_CALL);
	
	opcode->set_op1(obj);
	opcode->set_op2(method_name);
	opcode->set_handler(&TodoExecutor::method_call);
	
	ops.add(opcode);	
}

/**
 * Registers a function
 * @param type the return type
 * @param name the function name
 */
void TodoCompiler::register_func(TodoType type, TodoData& name)
{
	std::string *func_name;
	todo::todo_opcode *opcode = new todo::todo_opcode(TODO_DECL_FUNC);
	
	opcode->set_op1(name);
	opcode->set_handler(&todo::TodoExecutor::declare_func);
	opcode->info = new todo::TodoFunction;
	
	((TodoFunction*)opcode->info)->op_num = ops.get_num();
	
	ops.add(opcode);

	opcode_ptr = opcode;
}

/**
 * Delimiters the function TodoOpcodes
 */
void TodoCompiler::end_register_func(void)
{
	todo::todo_opcode *opcode = new todo::todo_opcode(TODO_END_FUNC);
	todo::todo_opcode *opcode_func = opcode_ptr;
	std::map<std::string, TodoType>::iterator it;
	
	opcode->set_handler(&todo::TodoExecutor::free_func);
	ops.add(opcode);
	
	((TodoFunction*)opcode_func->info)->last_opcode = ops.get_num()-1;
}

/**
 * Performs the function call
 * @param name the function name
 */
TodoData TodoCompiler::func_call(TodoData& name)
{
	std::string *func_name;
	todo::todo_opcode *opcode = new todo::todo_opcode(TODO_FUNC_CALL);
	TodoData result(TODO_RET_VALUE);
	
	opcode->set_op1(name);
	opcode->set_handler(&todo::TodoExecutor::func_call);
	opcode->info = new todo::todo_func_call;
	
	ops.add(opcode);
	
	return result;
}

void TodoCompiler::add_func_call_arg(TodoData& arg)
{	
	TodoValue* val_copy = new TodoValue;
	todo::todo_opcode* func = ops.get_last();
	
	(*val_copy).type = arg.data.type;
	(*val_copy).val = arg.data.val;
	if (val_copy->type == TODO_STRING) {
		val_copy->val.str = new std::string(*arg.data.val.str);
	}
	
	((todo_func_call*)func->info)->args.push_back(val_copy);
}

void TodoCompiler::add_func_arg(TodoType type, TodoData& arg)
{	
	const todo::todo_opcode* func = ops.get_last();
	
	((TodoFunction*)func->info)->args.insert(std::pair<std::string, TodoType>(*arg.data.val.str, type));
}

void TodoCompiler::register_if(TodoData& result, TodoData& expr)
{
	todo::todo_opcode *if_expr = new todo::todo_opcode(TODO_IF, expr);
	
	if_expr->set_handler(&todo::TodoExecutor::if_stmt);
	
	ops.add(if_expr);
	
	result.type = TODO_JMP;
	result.data.val.tint = ops.get_num()-1;	
}

void TodoCompiler::register_elsif(TodoData& last_expr, const TodoData& elsif_expr, TodoData& result) const
{
	todo::todo_opcode *elsif_op = new todo::todo_opcode(TODO_ELSIF, elsif_expr);
	
	elsif_op->set_handler(&todo::TodoExecutor::elsif_stmt);
	
	ops.add(elsif_op);
	
	ops.get_opcode(last_expr.data.val.tint)->op2.type = TODO_JMP;
	ops.get_opcode(last_expr.data.val.tint)->op2.data.val.tint = ops.get_num()-2;
	
	result.type = last_expr.type = TODO_JMP;
	result.data.val.tint = last_expr.data.val.tint = ops.get_num()-1;
}

void TodoCompiler::register_else(const TodoData& if_or_elsif_expr, TodoData& result) const
{
	todo::todo_opcode *else_op = new todo::todo_opcode(TODO_ELSE);
	
	else_op->set_handler(&todo::TodoExecutor::else_stmt);
	
	ops.add(else_op);
	
	result.data.val.tint = ops.get_num()-1;

	ops.get_opcode(if_or_elsif_expr.data.val.tint)->op2.type = TODO_JMP;
	ops.get_opcode(if_or_elsif_expr.data.val.tint)->op2.data.val.tint = result.data.val.tint;
}

void TodoCompiler::register_endif(const TodoData& end_expr) const
{
	todo::todo_opcode *endif_op = new todo::todo_opcode(TODO_ENDIF);
	
	endif_op->set_handler(&todo::TodoExecutor::endif_stmt);
	
	ops.add(endif_op);

	ops.get_opcode(end_expr.data.val.tint)->op2.type = TODO_JMP;
	ops.get_opcode(end_expr.data.val.tint)->op2.data.val.tint = ops.get_num()-1;
}

/**
 * Dump TodoOpcodes
 */
void TodoCompiler::debug(void) const
{
	ops.dump();
}

/**
 * Initializes the execution
 */
void TodoCompiler::finalize(void) const
{
#ifdef TODO_DEBUG
	debug();
#endif
	executor.init();
	executor.run();
}
} // todo
